#include <windows.h>
#include <string.h>
#include <fcntl.h>
#include <io.h>

#include "lib.h"
#include "md4.h"
#include "pkgdesc.h"
#include "zipfile.h"
#include "stream.h"
#include "pathname.h"
#include "pkg.h"
#include "mfestprv.h"
#include "pchmfest.h"
#include "pkg.h"
#include "client.h"
#include "admpipe.h"

#define SEND_ATTR 
#define TEMPFILE "psrv0002.tmp"

PackageDescription::PackageDescription(const String& pkg, uint32 version, const String& zip)
{
#ifdef ADMLIST_SENDALL
	adminSendSize = 8;	// bare minimum (null char*s) 
#else	// only zipnames
	adminSendSize = 3;	// bare minimum (null char*s) 
#endif

	m_contents 				= (TCHAR *)0;
	m_num_files 			= 0;
	m_strong_checksums		= (t_md4 *)0;
	next = (PackageDescription*)0;
	set(pkg, version, zip);
    m_refCount = 0;
}

PackageDescription::PackageDescription()
{	
#ifdef ADMLIST_SENDALL
	adminSendSize = 8;	// bare minimum (null char*s) 
#else	// only zipnames
	adminSendSize = 3;	// bare minimum (null char*s) 
#endif

	m_contents 				= (TCHAR *)0;
	m_num_files 			= 0;
	m_strong_checksums		= (t_md4 *)0;
	next = (PackageDescription*)0;
	set("", 0, "");

    m_refCount = 0;
}

bool8
PackageDescription::set(const String& name, uint32 version, const String& zip)
{
	int32 len;
	int fh;
	char *nzip;
	int32 newSendSize, newAdminSendSize;

	newSendSize = 2;			// pkg list item msg len + id byte
	newAdminSendSize = 2;		// ditto

	len = name.getLength();
	if (len)
	{
		newSendSize += len;			// count + name bytes (no null)
#ifdef ADMLIST_SENDALL
		newAdminSendSize += len;	// ditto
#endif
	}
	else
	{
		++newSendSize;			// just count==0
#ifdef ADMLIST_SENDALL
		++newAdminSendSize;		// just count==0
#endif
	}

	pkg.setLocalVersion(version);

	newSendSize += sizeof(uint32);
#ifdef ADMLIST_SENDALL
	newAdminSendSize += sizeof(uint32);
#endif

	len = zip.getLength();
	nzip = (char*)(len ? new char[len+1] : 0);
	if (nzip)
	{
		StringConversions::convert(nzip, zip, len+1);

		// normal client does NOT get zipfile (sendSize doesn't change)
		newAdminSendSize += len;	// admin gets it: count+name bytes (no null)
	}
	else
		++newAdminSendSize;		// just count==0

	int32 zipsize = 0;
	if (nzip)
	{
		fh = _open(nzip, O_RDONLY | O_BINARY);
		if (fh != -1)
		{
			zipsize = (uint32)filelength(fh);
			if (zipsize == (uint32)-1)
				zipsize = 0;

			_close(fh);
		}
	}
	delete nzip;
	pkg.setZipSize(zipsize);

	newSendSize += sizeof(uint32);		// normal client gets zipsize
	// admin does NOT get zipsize (adminSendSize doesn't change)

	if (name.getLength() && zip.getLength())
	{

		pkg.setName(name);
		zipfile = zip;
		adminSendSize = newAdminSendSize;

		// CSN-7534: String version
		// Version as string is obtained from zip name: "Package_Name v{VERSION}.zip"
		String sAux = name + String(" v");
		uint32 iIndex1 = zip.find(sAux);
		uint32 iIndex2 = zip.find(String(".zip"));
		String sVersion = zip.substring(iIndex1+sAux.getLength(), iIndex2-iIndex1-sAux.getLength());
		pkg.setVersionStr(sVersion);

		if(!FindZipContents())
		{
			// if this call fails, it cleans up m_contents for us
			return false;
		}

		if (!FindDependencies()) 
		{	//dependencies are optional
		}

		pkg.setZipSize(zipsize);	//reset zipsize to the correct size

		if(!FindTotalPackageSize())
		{
			// if this call fails, m_contents could still be allocated.
			delete [] m_contents;
			m_contents = (TCHAR *)0;

			return false;
		}

		if(!FindStrongChecksums())
		{
			// if this call fails, m_checksums is cleaned up for us,
			//  but m_contents could still be allocated.
			delete [] m_contents;
			m_contents = (TCHAR *)0;

			return false;
		}

		return true;
	}
	return false;
}

bool8
PackageDescription::send(Client* client, bool8 unicode)
{
#ifndef ADMIN

	uint8 depends;
	client->msgStart();

	if (unicode)
		client->msgAdd((uint8*)"ML", 2);	// pkg list item id bytes for multibyte...distinguish from errors
	else
		client->msgAdd((uint8)'L');	// pkg list item id byte...distinguish from errors

	client->msgAdd(pkg.getName(), unicode);
	
	client->msgAdd((int32)pkg.getLocalVersion());
	client->msgAdd((int32)pkg.getZipSize());

	client->msgAdd((int32)getUnzippedPkgSize());

	//attributes: currently dependencies are the only attributes
	depends = (uint8)getNumDependencies();
	client->msgAdd(depends);

	for(uint8 i=0;i<depends;i++)
	{
		char attributename[256];
		char attributevalue[256];

		sprintf(attributename, "Requires: %S", (const uint16*)getDepencies()[i].getName());
		sprintf(attributevalue, "0x%X", getDepencies()[i].getRequiredVersion());

		client->msgAdd(attributename, unicode);

		client->msgAdd(attributevalue, unicode);
	}

	return client->msgEnd();
#else
	return true;
#endif
}

bool8
PackageDescription::adminSend(AdminPipe* client, bool8 unicode)
{
#ifndef ADMIN
	client->msgStart();
	if (unicode)
		client->msgAdd((uint8)'M');
	client->msgAdd((uint8)'L');

#ifdef ADMLIST_SENDALL
	client->msgAdd(name, unicode);
//	len = name ? strlen(name) : 0;
//	*OBuf++ = (uint8)(len & 0xff);
//	strcpy((char*)OBuf, name);
//	OBuf += len;

	*OBuf++ = (uint8)((version >> 24) & 0xff);
	*OBuf++ = (uint8)((version >> 16) & 0xff);
	*OBuf++ = (uint8)((version >> 8) & 0xff);
	*OBuf++ = (uint8)(version & 0xff);
#endif

	client->msgAdd(zipfile, unicode);
	return client->msgEnd();
#else
	return true;
#endif
}

bool8
PackageDescription::FindZipContents()
{
	m_num_files = 0;

	ZipFile zip;
	if(!zip.open(zipfile))
	{
		// couldn't open the zip file for reading!
		return false;
	}

	ZipEntry *head, *curr;
	head = zip.getZipEntries();
	curr = head;

	// find the total length of all the zip filenames
	uint32 totallen = 0;
	while(curr)
	{
		totallen += (curr->filename.getLength() + 1);
		curr = curr->getNext();
		m_num_files++;
	}
	totallen += 1;

	delete [] m_contents;
	m_contents = new TCHAR[totallen];
	if(!m_contents)
	{
		// couldn't allocate the memory!
		zip.close();
		return false;
	}

	// now iterate back over the list and copy them into m_contents
	TCHAR*dst = m_contents;
	curr = head;
	while(curr)
	{
		wcscpy(dst, (TCHAR*)(const uint16*)curr->filename);
		dst += (wcslen(dst) + 1);

		curr = curr->getNext();
	}
	*dst = '\0';
	zip.close();

	return true;
}

bool8	
PackageDescription::FindTotalPackageSize()
{
	ZipFile zip;
	if(!zip.open(zipfile))
	{
		// couldn't open the zip file for reading!
		return false;
	}

	ZipEntry *curr;
	curr = zip.getZipEntries();

	// find the total length of all the uncompressed zip files
	uint32 totallen = 0;
	while(curr)
	{
		totallen += (curr->uncompressedSize);
		curr = curr->getNext();
	}

	zip.close();

	pkg.setUnZipSize(totallen);

	return true;
}

pCStream
PackageDescription::FindUnzippedFile(const String& filename)
{
#if 1

	return newCStream(filename, streamO_RDONLY);

#else
	pCStream retval;
	HANDLE hFind;
	WIN32_FIND_DATA wfd;
	bool8 more;

	// perform a search for the file...

	// see if it's in the current directory first...
	hFind = FindFirstFile(filename, &wfd);

	if(hFind != INVALID_HANDLE_VALUE)
	{
		// here it is!
		retval = newCStream((char *)filename, streamO_RDONLY);
		FindClose(hFind);
		return retval;
	}
	else
	{
		// it's not in the current directory, so check 
		//  each subdiretory
	}

	PathName wildcard;
	PathName sub;

	wildcard.set("*");

	hFind = FindFirstFile(wildcard, &wfd);
	more = (bool8)(hFind != INVALID_HANDLE_VALUE);
	while (more)
	{
		if (wfd.cFileName[0] == '.')
		{
			if (wfd.cFileName[1] == '\0' ||
				wfd.cFileName[1] == '.' && wfd.cFileName[2] == '\0')
			{
				more = (bool8)(FindNextFile(hFind, &wfd) != 0);
				continue;
			}
		}

		if (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
		{
			sub.set(wfd.cFileName);
			sub.concat(filename);

			return FindUnzippedFile(sub);
		}

		more = (bool8)(FindNextFile(hFind, &wfd) != 0);
	}

	if (hFind != INVALID_HANDLE_VALUE)
	{
		bool8 err = (bool8)(GetLastError() != ERROR_NO_MORE_FILES);  

		FindClose(hFind);

		if (err)
			return (pCStream)0;
	}

	return (pCStream)0;
#endif
}

bool8
PackageDescription::FindStrongChecksum(pCStream file, t_md4 *md4)
{
	try
	{
		uint8 buffer[512];
		int32 read;

		MDstruct md4struct;
		MDbegin(&md4struct);

		do
		{
			read = CStreamRead(file, buffer, 64);

			if(streamEOF != read)
				MDupdate(&md4struct, buffer, read * 8);
			else
				MDupdate(&md4struct, buffer, 0);

		} while(read == 64);

		(*md4)[0] = md4struct.buffer[0];
		(*md4)[1] = md4struct.buffer[1];
		(*md4)[2] = md4struct.buffer[2];
		(*md4)[3] = md4struct.buffer[3];

		return true;
	}
	catch(...)
	{
		return false;
	}
}

bool8	
PackageDescription::FindStrongChecksums()
{
	delete [] m_strong_checksums;
	m_strong_checksums = (t_md4 *)0;

	m_strong_checksums = new t_md4[m_num_files];
	if(!m_strong_checksums)
	{
		return false;
	}

	// iterate over the zip contents (in the order they appear in the
	//  zipfile) and find those files in the appropriate directory
	//  under the packages directory.  open the file, find the md4 digest
	//  and save it. this ensures the package contents and their 
	//  corresponding strong checkms are in the same order.

	ZipFile zip;
	if(!zip.open(zipfile))
	{
		// couldn't open the zip file for reading!
		delete [] m_strong_checksums;
		m_strong_checksums = (t_md4 *)0;
		return false;
	}

	ZipEntry *curr;
	curr = zip.getZipEntries();

	int i = 0;
	pCStream file;
	while(curr)
	{
		PathName unzipdir;
		unzipdir.set(zipfile);
		unzipdir.shrink(4);
		unzipdir.concat(curr->filename);

		file = FindUnzippedFile(unzipdir);

		if(file)
		{
			if(!FindStrongChecksum(file, &m_strong_checksums[i]))
			{
				deleteCStream(file);
				delete [] m_strong_checksums;
				m_strong_checksums = (t_md4 *)0;
				zip.close();
				return false;
			}
		}
		else
		{
			deleteCStream(file);
			delete [] m_strong_checksums;
			m_strong_checksums = (t_md4 *)0;
			zip.close();
			return false;
		}

		deleteCStream(file);
		i++;
		curr = curr->getNext();
	}

	zip.close();

	return true;
}

bool8
PackageDescription::FindDependencies()
{
	ZipFile zip;
	ZipEntry* probe;
	pCStream destMFest = (pCStream)0;
	UnzipStream* srcMFest = (UnzipStream*)0;
	uint8 buf[4096];
	int32 amt;
	PMHead pmh;
	PMPackage pmp;

	if(!zip.open(zipfile))
	{
		// couldn't open the zip file for reading!
		return false;
	}

	probe = zip.getZipEntries();
	while (probe)
		if (probe->filename.find(L"installed/") != String::NPOS)
			break;
		else
			probe = probe->getNext();

	if (!probe)
		goto abort;

	destMFest = newCStream(TEMPFILE, 0);
	if (!destMFest)
		goto abort;

	srcMFest = probe->getUnzipStream(&zip);
	if (!srcMFest)
		goto abort;

	while ((amt = srcMFest->read(buf, 4096)) >= 0)
		if (amt > 0 && amt != CStreamWrite(destMFest, buf, amt))
		{
			amt = -2;
			break;
		}

	zip.close();

	if (amt < -1)	// problem writing
		goto abort;
	
	delete srcMFest;
	srcMFest = (UnzipStream*)0;

	// read mfest into pkg
	//
	if (streamEOF == CStreamSeek(destMFest, 0, streamSEEK_SET))
		goto abort;

	if (!pmh.load(destMFest))
		goto abort;

	if (pmh.NumPkgs != 1)
		goto abort;

	if (!pmp.load(destMFest, pmh, &pkg))
		goto abort;

	deleteCStream(destMFest);
	CStreamDelFile((char*)(const char*)TEMPFILE);

	return true;

abort:
	delete srcMFest;
	deleteCStream(destMFest);
	CStreamDelFile((char*)(const char*)TEMPFILE);

	return false;
}

bool8
PackageDescription::setZipFile(const String& Zip)
{
	PatchPackageManifest pm;
	ZipFile zf;

	if (!zf.open(Zip))
		return false;

	if (!pm.load(pkg, zf))
		return false;

	if (!pkg.getName())
		return false;

	set(pkg.getName(), pkg.getLocalVersion(), Zip);

	return true;
}

void
PackageDescription::setUnknown()
{
	pkg.setName("(unknown)");
	if (zipfile.getLength() == 0)
		zipfile = "(unknown)";
}


void
PackageDescription::copy(PackageDescription* toCopy)
{
	zipfile = "";

	pkg.setLocalVersion(toCopy->getVersion());

	if (toCopy->getZipName())
	{
		zipfile = toCopy->getZipName();
	}

	pkg.setName(toCopy->getName());
}